/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024.
 * Description: a high performance way to acquire cpu and memory stat
 * Author: Pang LiYuan
 * Create: 2024-04-07
 */
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/sched/mm.h>
#include <linux/types.h>
#include <linux/security.h>
#include <linux/audit.h>
#include <linux/swap.h>
#include <linux/vmstat.h>
#include <linux/io.h>
#include <linux/rtos_hpstat.h>

/* use this fator to convert cntvct_el0 to microseconds */
static u64 g_cntvct_fator;

static int hp_stat_mmap(struct file *filp, struct vm_area_struct *vma)
{
	unsigned long pfn;
	unsigned long vmsize;

	if (!capable(CAP_SYS_ADMIN)) {
		pr_err("hpstat permission check failed\n");
		return -EACCES;
	}
	pfn = virt_to_pfn(vm_zone_stat);

	vmsize = vma->vm_end - vma->vm_start;
	if (vmsize != PAGE_SIZE || vma->vm_pgoff != 0) {
		pr_err("hpstat mmap failed(vmsize != PAGE_SIZE)\n");
		return -EINVAL;
	}

	if (vma->vm_flags & VM_WRITE) {
		pr_err("hpstat mmap failed, only read allowed\n");
		return -EPERM;
	}

	if (remap_pfn_range(vma, vma->vm_start, pfn, vmsize, vma->vm_page_prot)) {
		pr_err("hpstat mmap failed(remap_pfn_range memory failed)\n");
		return -EAGAIN;
	}
	return 0;
}

static const struct file_operations fops = {
	.owner = THIS_MODULE,
	.mmap = hp_stat_mmap,
};

static struct miscdevice dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "hp_stat",
	.fops = &fops,
	.mode = 0440,
};

static int __init rtos_hpstat_init(void)
{
	int ret = misc_register(&dev);

	if (ret != 0) {
		pr_err("failed to register misc device");
		return ret;
	}

	return 0;
}

late_initcall(rtos_hpstat_init);

static void __init rtos_init_cntvct_fator(void)
{
	g_cntvct_fator = read_sysreg(cntfrq_el0) / (u64)USEC_PER_SEC;
	if (g_cntvct_fator == 0)
		pr_warn("hpstat: cntfrq_el0 has insufficient precision!");
}

early_initcall(rtos_init_cntvct_fator);

#ifdef CONFIG_RTOS_HP_OBTAIN_STAT_CPU
/*
 * we want to use lock-free atomic instructions here to improve performance, this is to
 * ensure that it will generate lock-free atomic instructions instead of functions with
 * locks.
 */
static_assert(__atomic_always_lock_free(sizeof(u64), 0), "rtos hpstat lock free check failed!");

/**
 * rtos_hpstat_save_entry_idle - save the entry time when CPU enter idle
 * @cpu: which cpu we save info for
 *
 * Here we use u64 to store three type information, its structure be like:
 * | is_in_idle | enter idle time | total idle time |
 * |     63     |    62 - 32      |     31 - 0      |
 * In this function, we save "enter idle time" and set "is_in_idle" to 1
 */
void rtos_hpstat_save_entry_idle(int cpu)
{
	u64 temp_idle_timeset;

	if (g_cntvct_fator == 0 || WARN_ONCE(cpu >= RTOS_CPU_USAGE_MAX_CPU,
			"cpu %d over max %d\n", cpu, RTOS_CPU_USAGE_MAX_CPU))
		return;

	/* cpu read its own 'cpu_idle_timeset' won't casue concurrency issues */
	temp_idle_timeset = *(u64 *)&cpu_idle_timeset[cpu];
	temp_idle_timeset |= ((read_sysreg(cntvct_el0) / g_cntvct_fator) << 32) | ((u64)1 << 63);
	__atomic_store_8(&cpu_idle_timeset[cpu], temp_idle_timeset, __ATOMIC_RELAXED);
}

/**
 * rtos_hpstat_save_total_idle - calculate and save total idle time
 * @cpu: which cpu we save info for
 *
 * Here we use u64 to store three type information, its structure be like:
 * | is_in_idle | enter idle time | total idle time |
 * |     63     |    62 - 32      |     31 - 0      |
 * In this function, we save "total idle time" and set both "is_in_idle" and
 * "enter idle time" to zero
 */
void rtos_hpstat_save_total_idle(int cpu)
{
	u64 temp_idle_timeset, last_time, now_time;

	if (g_cntvct_fator == 0 || cpu >= RTOS_CPU_USAGE_MAX_CPU)
		return;

	/* cpu read its own 'cpu_idle_timeset' won't casue concurrency issues */
	temp_idle_timeset = *(u64 *)&cpu_idle_timeset[cpu];

	last_time = (temp_idle_timeset >> 32) & (((u64)1 << 31) - 1);
	now_time = (read_sysreg(cntvct_el0) / g_cntvct_fator) & (((u64)1 << 31) - 1);

	temp_idle_timeset += (now_time - last_time) & (((u64)1 << 31) - 1);
	/* clear 32~63 bits, we don't need those bit any more */
	temp_idle_timeset &= ((u64)1 << 32) - 1;
	__atomic_store_8(&cpu_idle_timeset[cpu], temp_idle_timeset, __ATOMIC_RELAXED);
}
#endif
